#!/bin/bash
#       Name: SearchSploit - Exploit-DB's CLI search tool
#    Version: 3.3  (Release date: 2016-04-02)
# Written by: Offensive Security, Unix-Ninja & g0tmi1k
#   Homepage: https://github.com/offensive-security/exploit-database
#
## NOTE:
#   Exit code '0' means finished normally
#   Exit code '1' means something went wrong
#   Exit code '2' means finished help screen
#   Exit code '6' means updated from GitHub


## OS settings
gitpath="/usr/share/exploitdb"
csvpath="${gitpath}/files.csv"


## Program settings
gitremote="https://github.com/offensive-security/exploit-database.git"
progname="$( basename "$0" )"


## Default options
COLOUR=1
EDBID=0
EXACT=0
FILEPATH=1
GETPATH=0
OVERFLOW=0
WEBLINK=0
SCASE=0
TAGS=""
CASE_TAG_GREP="-i"
CASE_TAG_FGREP="tolower"
COLOUR_TAG=""
SEARCH=""


## Set LANG variable to avoid illegal byte sequence errors
LANG=C


## If files.csv is in the searchsploit path, use that instead
if [[ -f "$( dirname "$0" )/files.csv" ]]; then
  gitpath="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
  csvpath="${gitpath}/files.csv"
fi


## Usage info
function usage()
{
  echo "  Usage: ${progname} [options] term1 [term2] ... [termN]"
  echo "Example:"
  echo "  ${progname} afd windows local"
  echo "  ${progname} -t oracle windows"
  echo
  echo "========="
  echo " Options "
  echo "========="
  echo "   -c, --case      Perform a case-sensitive search (Default is inSEnsITiVe)."
  echo "   -e, --exact     Perform an EXACT match on exploit title (Default is AND) [Implies \"-t\"]."
  echo "   -h, --help      Show this help screen."
  echo "   -o, --overflow  Exploit title's are allowed to overflow their columns."
  echo "   -p, --path      Show the full path to an exploit (Copies path to clipboard if possible)."
  echo "   -t, --title     Search just the exploit title (Default is title AND the file's path)."
  echo "   -u, --update    Update exploit database from git."
  echo "   -w, --www       Show URLs to Exploit-DB.com rather than local path."
  echo "       --colour    Disable colour highlighting."
  echo "       --id        Display EDB-ID value rather than local path."
  echo
  echo "======="
  echo " Notes "
  echo "======="
  echo " * Use any number of search terms."
  echo " * Search terms are not case sensitive, and order is irrelevant."
  echo "   * Use '-c' if you wish to reduce results by case-sensitive searching."
  echo "   * And/Or '-e' if you wish to filter results by using an exact match."
  echo " * Use '-t' to exclude the file's path to filter the search results."
  echo "   * Remove false positives (especially when searching numbers/major versions)."
  echo " * When updating from git or displaying help, search terms will be ignored."
  echo ""
  exit 2
}


## Update database (via GIT)
function update()
{
  ## Make sure we are in the correct folder
  mkdir -p "${gitpath}/"
  cd "${gitpath}/"

  ## Are we in a git repo?
  if [[ "$( git rev-parse --is-inside-work-tree )" != "true" ]]; then
    if [[ "$( ls )" = "" ]]; then
      # If directory is empty, just clone
      echo -e '\n[i] Nothing here. Starting fresh...'
      git clone "${gitremote}" .
    fi
  fi

  # Is our git remote added? (aka homebrew)
  if [[ "$( git remote -v )" != *"${gitremote}"* ]]; then
    echo -e '\n[i] Missing git remote:' "${gitremote}"
    git init >/dev/null
    git remote add origin "${gitremote}" 2>/dev/null
  fi

  # Make sure to prep checkout first
  git checkout -- .

  # Update from git
  git pull origin master

  # If conflicts, clean and try again
  if [[ "$?" -ne 0 ]]; then
    git clean -d -fx ""
    git pull origin master
  fi

  echo -e "\n[*] Update finished."
  exit 6
}


## Printing dotted lines in the correct manner
function drawline()
{
  printf "%0.s-" $( eval echo {1..$(( COL1 + 1 ))} )
  echo -n " "
  printf "%0.s-" $( eval echo {1..$(( COL2 - 1 ))} )
  echo ""
}


## Check for empty args
if [[ $# -eq 0 ]]; then
  usage >&2
fi


## Parse long arguments
ARGS="-"
for param in "$@"; do
  if [[ "${param}" == "--case" ]]; then
    SCASE=1
  elif [[ "${param}" == "--exact" ]]; then
    EXACT=1
  elif [[ "${param}" == "--help" ]]; then
    usage >&2
  elif [[ "${param}" == "--overflow" ]]; then
    OVERFLOW=1
  elif [[ "${param}" == "--path" ]]; then
    GETPATH=1
  elif [[ "${param}" == "--title" ]]; then
    FILEPATH=0
  elif [[ "${param}" == "--update" ]]; then
    update
  elif [[ "${param}" == "--www" ]]; then
    WEBLINK=1
  elif [[ "${param}" == "--colour" ]] || [[ "${param}" == "--color" ]]; then
    COLOUR=""
  elif [[ "${param}" == "--id" ]]; then
    EDBID=1
  else
    if [[ "${param:0:1}" == "-" ]]; then
      ARGS=${ARGS}${param:1}
      shift
      continue
    fi
    TAGS="${TAGS} ${param//\`/_}"
  fi
done


## Parse short arguments
while getopts "cehoptuw" arg "${ARGS}"; do
  if [[ "${arg}" = "?" ]]; then
    usage >&2;
  fi
  case ${arg} in
    c) SCASE=1;;
    e) EXACT=1;;
    h) usage >&2;;
    o) OVERFLOW=1;;
    p) GETPATH=1;;
    t) FILEPATH=0;;
    u) update;;
    w) WEBLINK=1;;
  esac
  shift $(( OPTIND - 1 ))
done


## Print the full path. If pbcopy/xclip is available then copy to the clipboard
if [[ "${GETPATH}" -eq 1 ]]; then
  ## Get EDB-ID from input
  edbdb="$( echo ${TAGS} | tr -dc '0-9' )"
  ## Check files.csv
  location=$( cut -d, -f2 "${csvpath}" | grep -m 1 -E "/${edbdb}(\..*)?$" )
  title=$( grep -m 1 "${location}" "${csvpath}" | cut -d, -f3 | sed 's/"//g')
  ## Join paths
  location="${gitpath}/${location}"

  ## Did we find the exploit?
  if [[ -f "${location}" ]]; then
    ## Display out
    echo "Exploit: ${title}"
    echo "   Path: ${location}"
    echo ""

    ## Are any copy programs available?
    if hash xclip 2>/dev/null || hash pbcopy 2>/dev/null; then
      ## Linux
      if hash xclip 2>/dev/null; then
        echo -ne "${location}" | xclip -selection clipboard
        echo "Copied the file path to the clipboard."
      ## OSX
      elif hash pbcopy 2>/dev/null; then
        echo -ne "${location}" | pbcopy
        echo "Copied the file path to the clipboard."
      fi
    fi

    ## Done
    exit 0
  else
    ## Feedback
    echo "Could not find exploit EDB-ID #${edbdb}"

    ## Quit
    exit 1
  fi
fi


## If we are doing an exact match, do not check folder path.
if [[ "${EXACT}" -eq 1 ]]; then
  FILEPATH=0
fi


## Case sensitive?
if [[ "${SCASE}" -eq 1 ]]; then
  ## Remove the default flags
  CASE_TAG_GREP=""
  CASE_TAG_FGREP=""
fi


## Dynamically set column widths to the current screen size
if [[ "${WEBLINK}" -eq 1 ]]; then
  COL2=45
else
  COL2=$(( ${#gitpath} + 15 ))
fi
COL1=$(( $( tput cols ) - COL2 - 1 ))


## Print header
drawline
printf "%-${COL1}s %s" " Exploit Title"
if [[ "${WEBLINK}" -eq 1 ]]; then
  echo "|  URL"
elif [[ "${EDBID}" -eq 1 ]]; then
  echo "|  EDB-ID"
else
  echo "|  Path"
  printf "%-${COL1}s "
  echo "| (${gitpath}/platforms)"
fi
drawline


## EXACT search command?
if [[ "${EXACT}" -eq 1 ]]; then
  ## Case sensitive?
  if [[ "${SCASE}" -eq 1 ]]; then
    SEARCH="${TAGS}"
  else
    SEARCH="$( echo ${TAGS} | tr '[:upper:]' '[:lower:]' )"
  fi

  ## Remove leading space
  SEARCH="$(echo ${SEARCH} | sed -e 's/^[[:space:]]//')"

  ## If we are to use colour, add the values to search for
  if [[ "${COLOUR}" -eq 1 ]]; then
    COLOUR_TAG="${SEARCH}"
  fi
## or AND search command?
else
  ## For each term
  for tag in ${TAGS}; do
    ## If we are to use colour, add the values to search for between "or"
    if [[ "${COLOUR}" -eq 1 ]]; then
      if [[ "${COLOUR_TAG}" ]]; then
        COLOUR_TAG="${COLOUR_TAG}\|"
      fi
      COLOUR_TAG="${COLOUR_TAG}${tag}"
    fi

    ## Search both title and path?
    if [[ "${FILEPATH}" -eq 1 ]]; then
      ## Is there a value already?
      if [[ "${SEARCH}" ]]; then
        SEARCH="${SEARCH} |"
      fi

      ## Search command for each term
      SEARCH="${SEARCH} fgrep ${CASE_TAG_GREP} \"${tag}\""
    ## Search just the title, not the path
    else
      ## If there is already a value, prepend text to get ready
      if [[ "${SEARCH}" ]]; then
        SEARCH="${SEARCH}/ && ${CASE_TAG_FGREP}(\$1) ~ /"
      fi

      ## Case sensitive?
      if [[ "${SCASE}" -eq 1 ]]; then
        SEARCH="${SEARCH}${tag}"
      else
        SEARCH="${SEARCH}$( echo ${tag} | tr '[:upper:]' '[:lower:]' )"
      fi
    fi
  done
fi


## If we are not to use the path name
if [[ "${FILEPATH}" -eq 0 ]]; then
  SEARCH="awk -F '[|]' '${CASE_TAG_FGREP}(\$1) ~ /${SEARCH}/ {print}'"
fi


## If we are to use colour, add the value here
if [[ "${COLOUR_TAG}" ]]; then
  SEARCH="${SEARCH} | grep --color=always -ie \"\${COLOUR_TAG}\""
fi


## Search, format, and print results
if [[ "${OVERFLOW}" -eq 1 ]]; then
  FORMAT=${COL1}
else
  FORMAT=${COL1}'.'${COL1}
fi


## Magic search Fu
## Web link format?
if [[ "${WEBLINK}" -eq 1 ]]; then
  awk -F "\"*,\"*" '{ printf "%-'${FORMAT}'s | %s\n", $3, "https://www.exploit-db.com/exploits/"$1"/"}' "${csvpath}" \
    | eval "${SEARCH}"
## Just the EDB-ID?
elif [[ "${EDBID}" -eq 1 ]]; then
  awk -F "\"*,\"*" '{ printf "%-'${FORMAT}'s | %s\n", $3, $1}' "${csvpath}" \
    | eval "${SEARCH}"
## Default view
else
  awk -F "\"*,\"*" '{ printf "%-'${FORMAT}'s | %s\n", $3, $2}' "${csvpath}" \
    | eval "${SEARCH}" \
    | sed "s/| platforms/| ./"
fi


## Print footer
drawline


## Done
exit 0
